/// The main source file.
/*
 * drench-em.cpp
 *
 *  Created on: Dec 29, 2014
 */

#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define SCREEN_COLOUR_DEPTH 32

#include <SDL.h>

// Support for PNG etc.
#include <SDL_image.h>

// Support TTF
#include <SDL_ttf.h>

// std::string.
#include <string>

//The window we'll be rendering to
SDL_Window* f_sdlWindow = NULL;
SDL_Renderer* f_sdlRenderer = NULL;

SDL_Texture* f_sdlPlateTexture = NULL;
SDL_Rect f_sdlPlateRectangle;

SDL_GameController* f_sdlGameController = NULL;

TTF_Font* f_fontStatusFont = NULL;

int f_nXVelocity = 0;
int f_nYVelocity = 0;
float f_fVelocityFactor = 0.1;

// Anything below this number will be seen/registered as not moved.
int f_nGameControllerJoystickDeadZone = 3;

Uint32 f_nStartTicks = 0;
Uint32 f_nTotalFrames = 0;

int f_nWindowWidth = 0;
int f_nWindowHeight = 0;

bool f_bShowFrameRateOnScreen = true;

const int SCREEN_FPS = 50;
const int SCREEN_TICK_PER_FRAME = 1000 / SCREEN_FPS;

bool f_arButtonHasBeenReleasedSinceLast[SDL_CONTROLLER_BUTTON_MAX];


/**
 *
 */
Sint16 GetReducedGameControllerAxis(SDL_GameController *pGameController,
		SDL_GameControllerAxis nAxis, Sint16 nDeadZone) {
	Sint16 nReturnValue = 0;

	Sint16 nAxisState = SDL_GameControllerGetAxis(pGameController, nAxis);

	// This '1000' is just my own arbitrary value
	nReturnValue = round(nAxisState / 1000);
	// These next two lines are an attempt to make the sprite stable when the stick is in rest position.
	if ((nDeadZone >= nReturnValue) && (nReturnValue >= -nDeadZone)) {
		nReturnValue = 0;
	}

	// TODO Perhaps do a lookup in a table instead, since that might make it easier to make the speed higher the further you move the stick away from the center.

	return (nReturnValue);
}

bool UpdateAndProcessInput() {
	bool bGameActive = true;

	// I think this eats all the events since last pump, and then make a summary available.
	SDL_PumpEvents();

	/**
	 * This is the Keyboard state investigation.
	 */
	// NOTE: Do not release this pointer 'pKeyboardStateArray' the allocation belongs to SDL.
	const Uint8 *pKeyboardStateArray = SDL_GetKeyboardState(NULL);

	if (pKeyboardStateArray[SDL_SCANCODE_ESCAPE]) {
		bGameActive = false;
		printf("DDD ESC has been pressed.\n");
	}

	// TODO Loop the game controllers registered
	// TODO somehow identify which game controllers have been attached/detached.
	/**
	 * Game controller.
	 */

	f_nYVelocity = GetReducedGameControllerAxis(f_sdlGameController,
			SDL_CONTROLLER_AXIS_LEFTY, f_nGameControllerJoystickDeadZone);


	if (SDL_GameControllerGetButton(f_sdlGameController,
			SDL_CONTROLLER_BUTTON_DPAD_UP) == 1) {
		// Only flip the frame rate bool if the button has been released prior to pushing.
		if ( f_arButtonHasBeenReleasedSinceLast[SDL_CONTROLLER_BUTTON_DPAD_UP] ){
			f_bShowFrameRateOnScreen = !f_bShowFrameRateOnScreen;
			f_arButtonHasBeenReleasedSinceLast[SDL_CONTROLLER_BUTTON_DPAD_UP] = false;
		}
	} else if (SDL_GameControllerGetButton(f_sdlGameController,
			SDL_CONTROLLER_BUTTON_DPAD_UP) == 0) {
		f_arButtonHasBeenReleasedSinceLast[SDL_CONTROLLER_BUTTON_DPAD_UP] = true;
	}
	// TODO V add support for error handling on this.

	return (bGameActive);
}

bool MovePlayersAndAllOtherObjects(int nTimeStep) {
	bool bGameActive = true;

	// TODO C Make this call when the window is created.
	int nWindowWidth;
	int nWindowHeight;
	SDL_GetWindowSize(f_sdlWindow, &nWindowWidth, &nWindowHeight);

	// TODO later make the movement dependent on the time that has elapsed.
	f_sdlPlateRectangle.x += round(
			f_nXVelocity * nTimeStep * f_fVelocityFactor);
	if (f_sdlPlateRectangle.x < 0) {
		f_sdlPlateRectangle.x = 0;
	}
	if (f_sdlPlateRectangle.x > nWindowWidth - f_sdlPlateRectangle.w) {
		f_sdlPlateRectangle.x = nWindowWidth - f_sdlPlateRectangle.w;
	}

	f_sdlPlateRectangle.y += round(
			f_nYVelocity * nTimeStep * f_fVelocityFactor);
	if (f_sdlPlateRectangle.y < 0) {
		f_sdlPlateRectangle.y = 0;
	}
	if (f_sdlPlateRectangle.y > nWindowHeight - f_sdlPlateRectangle.h) {
		f_sdlPlateRectangle.y = nWindowHeight - f_sdlPlateRectangle.h;
	}

	return (bGameActive);
}
/**
 *
 * First the PNG image is loaded into a surface.
 *  The surface is then turned into a texture.
 *  The surface is then discarded and the texture is returned.
 */
SDL_Texture* LoadPngTexture(std::string sPngFilename) {

	// TODO V Validate that f_sdlRenderer != NULL

	// Texture to return.
	SDL_Texture* sdlReturnTexture = NULL;

	// Load the PNG image into a surface.
	SDL_Surface* sdlTmpSurface = IMG_Load(sPngFilename.c_str());
	if (sdlTmpSurface != NULL) {
		// Set the background See through thingy.
		// TODO V Check the return value.
		// TODO V Select e.g. a green hue of some sort to make the transparent colour.
		//SDL_SetColorKey(sdlTmpSurface, SDL_TRUE, 0);
		// create a texture from surface pixels.
		sdlReturnTexture = SDL_CreateTextureFromSurface(f_sdlRenderer,
				sdlTmpSurface);
		if (sdlReturnTexture == NULL) {
			printf(
					"!!! SDL_CreateTextureFromSurface() failed for '%s' - SDL Error: %s\n",
					sPngFilename.c_str(), SDL_GetError());
		} else {
			// TODO C change this to make it a different return class
			f_sdlPlateRectangle.w = sdlTmpSurface->w;
			f_sdlPlateRectangle.h = sdlTmpSurface->h;
		}
		// release the surface.
		SDL_FreeSurface(sdlTmpSurface);
	} else {
		printf("!!! IMG_Load() failed for '%s' - SDL_Image Error: %s\n",
				sPngFilename.c_str(), IMG_GetError());
	}

	return (sdlReturnTexture);
}

int SetBackgroundColour(SDL_Renderer* sdlRenderer) {
	int nStatus = 0;

	// TODO V Check return values-
	SDL_SetRenderDrawColor(sdlRenderer, 0, 100, 0, 255);
	SDL_RenderClear(sdlRenderer);
	SDL_RenderPresent(sdlRenderer);

	return (nStatus);
}

int PrepareForGame() {
	int nStatus = 0;

	f_nTotalFrames = 0;

//	f_sdlBackBuffer = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_COLOUR_DEPTH);
	//Create window
	nStatus = SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP,
			&f_sdlWindow, &f_sdlRenderer);
	/*
	 nStatus = SDL_CreateWindowAndRenderer(SDL_WINDOWPOS_UNDEFINED,
	 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOW_SHOWN, &f_sdlWindow, &f_sdlRenderer );
	 */
	if (nStatus == 0) {
		SDL_SetWindowTitle(f_sdlWindow, "Drench-em");

		int nWidth;
		int nHeight;
		SDL_GetWindowSize(f_sdlWindow, &f_nWindowWidth, &f_nWindowHeight);
		printf("DDD Window size: %dx%d\n", f_nWindowWidth, f_nWindowHeight);

		SDL_RendererInfo sdlRendererInformation;
		if (SDL_GetRendererInfo(f_sdlRenderer, &sdlRendererInformation) == 0) {
			nWidth = sdlRendererInformation.max_texture_width;
			nHeight = sdlRendererInformation.max_texture_height;
			printf("DDD Render size: %dx%d\n", nWidth, nHeight);
		}

	} else {
		nStatus = -1;
		printf("!!!  SDL_CreateWindowAndRenderer failed. SDL_Error: %s\n",
				SDL_GetError());
	}

	if (nStatus == 0) {
		nStatus = SetBackgroundColour(f_sdlRenderer);
	}

	if (nStatus == 0) {
		// Load support for PNG
		int nImgFlags = IMG_INIT_PNG;
		// TODO V Also includ IMG_Quit();
		if (!(IMG_Init(nImgFlags) & nImgFlags)) {
			nStatus = -2;
		}
	}

	if (nStatus == 0) {
		f_sdlPlateTexture = LoadPngTexture("stick.png");
		if (f_sdlPlateTexture == NULL) {
			nStatus = -3;
		} else {
			// This is the starting position.
			f_sdlPlateRectangle.x = 100;
			f_sdlPlateRectangle.y = 200;
		}

	}

	if (nStatus == 0) {
		if (TTF_Init() != -1) {
			// TODO Change font to a constant.
			f_fontStatusFont = TTF_OpenFont("fonts/Cinzel-Regular.otf", 24);
			if (f_fontStatusFont == NULL) {
				printf("!!! TTF_OpenFont() failed: %s\n", TTF_GetError());
			}
		} else {
			printf("!!! TTF_Init() failed: %s\n", TTF_GetError());
			nStatus = -4;
		}
	}

	if (nStatus == 0) {
		//Check for joysticks
		if (SDL_NumJoysticks() < 1) {
			printf("Warning: No joysticks connected!\n");
		} else {
			for (int i = 0; i < SDL_NumJoysticks(); ++i) {
				if (SDL_IsGameController(i)) {
					printf(
							"Joystick %i is supported by the game controller interface!\n",
							i);
				} else {
					printf("Joystick %i is not a game controller interface!\n",
							i);
				}
			}
			//Load joystick
			//gGameController = SDL_JoystickOpen(0);
			f_sdlGameController = SDL_GameControllerOpen(0);
			if (f_sdlGameController == NULL) {
				printf(
						"Warning: Unable to open game controller! SDL Error: %s\n",
						SDL_GetError());
			} else {
				printf("Found a valid controller, named: %s\n",
						SDL_GameControllerName(f_sdlGameController));
			}
		}

	}

	for(int nIndex = 0; nIndex < SDL_CONTROLLER_BUTTON_MAX; nIndex++) {
		f_arButtonHasBeenReleasedSinceLast[nIndex] = true;
	}

	return (nStatus);
} // end PrepareForGame

/**
 * If the x/y pos are negative it means it is relative to the lower right corner.
 */
void DrawOutlineText(SDL_Renderer *pRenderer, std::string sText, int nXpos,
		int nYpos, TTF_Font *pFont, Uint8 nRed, Uint8 nGreen, Uint8 nBlue) {

	SDL_Color sdlTextColor;
	sdlTextColor.r = nRed;
	sdlTextColor.g = nGreen;
	sdlTextColor.b = nBlue;

	//Render text surface
	SDL_Surface* textSurface = TTF_RenderText_Solid(pFont, sText.c_str(),
			sdlTextColor);
	if (textSurface != NULL) {
		//Create texture from surface pixels
		SDL_Texture *textTexture = SDL_CreateTextureFromSurface(pRenderer,
				textSurface);
		if (textTexture != NULL) {

			SDL_Rect sdlDestinationRectangle;
			//Get image dimensions
			sdlDestinationRectangle.w = textSurface->w;
			sdlDestinationRectangle.h = textSurface->h;
			// TODO calculate this from the window size and the text texture size.
			if (nXpos < 0) {
				sdlDestinationRectangle.x = f_nWindowWidth + nXpos
						- textSurface->w;

			} else {
				sdlDestinationRectangle.x = nXpos;
			}
			if (nXpos < 0) {
				sdlDestinationRectangle.y = f_nWindowHeight + nYpos
						- textSurface->h;

			} else {
				sdlDestinationRectangle.y = nYpos;
			}
			if (SDL_RenderCopy(pRenderer, textTexture, NULL,
					&sdlDestinationRectangle) != 0) {
				printf("!!!  SDL_RenderCopy failed: %s\n", SDL_GetError());
			}
			SDL_DestroyTexture(textTexture);
		} else {
			printf(
					"Unable to create texture from rendered text! SDL Error: %s\n",
					SDL_GetError());
		}

		//Get rid of old surface
		SDL_FreeSurface(textSurface);
	} else {
		printf("!!! Unable to render text surface! SDL_ttf Error: %s\n",
		TTF_GetError());
	}

}

// Update the graphics.
void DrawAllTheGraphicsOnTheScreen(Uint32 nGameLoopStartTime) {
	f_nTotalFrames++;

	// Set the background colour. (Green).
	SDL_SetRenderDrawColor(f_sdlRenderer, 0, 100, 0, 255);

	// Clear the current rendering target with the drawing color.
	if (SDL_RenderClear(f_sdlRenderer) != 0) {
		printf("!!!  SDL_RenderClear() failed. SDL_Error: %s\n",
				SDL_GetError());
	}

	// TODO V Check return value.
	SDL_RenderCopy(f_sdlRenderer, f_sdlPlateTexture, NULL,
			&f_sdlPlateRectangle);

	// Uint32 nDeltaTime = SDL_GetTicks() - f_nStartTicks;
	//Calculate and correct fps
	float avgFPS = f_nTotalFrames / ((SDL_GetTicks() - f_nStartTicks) / 1000.f);
	// Not shure why this is being done.
	if (avgFPS > 2000000) {
		avgFPS = 0;
	}

	//Set text to be rendered
	char timeText[100];
	snprintf(timeText, 100, "Average Frames Per Second: %4.0f", avgFPS);

	if (f_bShowFrameRateOnScreen) {
		DrawOutlineText(f_sdlRenderer, timeText, -1, -1, f_fontStatusFont, 100,
				100, 100);
	}

	// If frame finished early
	Uint32 frameTicks = SDL_GetTicks() - nGameLoopStartTime;
	if (frameTicks < SCREEN_TICK_PER_FRAME) {
		//Wait remaining time
		SDL_Delay(SCREEN_TICK_PER_FRAME - frameTicks);
	}

	// Display the back buffer.
	SDL_RenderPresent(f_sdlRenderer);

}

bool GameLoop(Uint32 nPreviousLoopStartTime) {
	bool bGameActive = true;

	Uint32 nGameLoopStartTime = SDL_GetTicks();

	// UpdateArtificialIntelligence();
	// UpdatePhysics();
	if (bGameActive) {
		bGameActive = UpdateAndProcessInput();
	}

	if (bGameActive) {
		// TODO do some protectionof the conversion, though for this to fail a loopk would have to take over 16 seconds.
		bGameActive = MovePlayersAndAllOtherObjects(
				(int) nGameLoopStartTime - nPreviousLoopStartTime);
	}

	if (bGameActive) {
		// TODO V Add a boolean as return value here as well.
		DrawAllTheGraphicsOnTheScreen(nGameLoopStartTime);
	}
	// RegulateFrameRate();

	return (bGameActive);
}

/**
 * This is here to enable dumping SDL information relevant for reporting issues.
 *
 *
 */
void ShowSdlInformation() {
	// Show SDL version
	SDL_version linked;
	SDL_GetVersion(&linked);
	printf("SDL version being used: %d.%d.%d\n", linked.major, linked.minor,
			linked.patch);
	// Show OS version
	printf("SDL Platform: %s\n", SDL_GetPlatform());
	// Show HW information
	// Performance
	printf("Performance: %ld counter: %ld Frequency: %ld\n",
			SDL_GetPerformanceCounter() * SDL_GetPerformanceFrequency(),
			SDL_GetPerformanceCounter(), SDL_GetPerformanceFrequency());

	// Show window options
	// Show render information
	int nNumberOfRenderers = SDL_GetNumRenderDrivers();
	printf("- Number of renderers: %d\n", nNumberOfRenderers);
	for (int nIndex = 0; nIndex < nNumberOfRenderers; nIndex++) {
		SDL_RendererInfo sdlRendererInformation;
		if (SDL_GetRenderDriverInfo(nIndex, &sdlRendererInformation) == 0) {
			printf("    [%d]: %s %dx%d\n", nIndex, sdlRendererInformation.name,
					sdlRendererInformation.max_texture_width,
					sdlRendererInformation.max_texture_height);
			// printf(" %d %d\n", sdlRendererInformation.flags, SDL_RENDERER_TARGETTEXTURE);
			if (sdlRendererInformation.flags & SDL_RENDERER_SOFTWARE)
				printf("             - SDL_RENDERER_SOFTWARE\n");
			if (sdlRendererInformation.flags & SDL_RENDERER_ACCELERATED)
				printf("             - SDL_RENDERER_ACCELERATED\n");
			if (sdlRendererInformation.flags & SDL_RENDERER_PRESENTVSYNC)
				printf("             - SDL_RENDERER_PRESENTVSYNC\n");
			if (sdlRendererInformation.flags & SDL_RENDERER_TARGETTEXTURE)
				printf("             - SDL_RENDERER_TARGETTEXTURE\n");
		} else {
			printf("!!! SDL_GetRenderDriverInfo() failed ! SDL_Error: %s\n",
					SDL_GetError());
		}

	}
	if (nNumberOfRenderers < 1) {
		printf("!!! SDL_GetNumRenderDrivers() failed ! SDL_Error: %s\n",
				SDL_GetError());
	}
}

/*
 #     #    #      ###   #     #
 ##   ##   # #      #    ##    #
 # # # #  #   #     #    # #   #
 #  #  # #     #    #    #  #  #
 #     # #######    #    #   # #
 #     # #     #    #    #    ##
 #     # #     #   ###   #     #
 */

int main(int argc, char* args[]) {
	int nStatus = 0;

	if (argc > 1) {
		printf("DDD argc: %d args: '%s'\n", argc, args[1]);
		if (strcmp(args[1], "--info") == 0) {
			printf("--info\n");
			ShowSdlInformation();
			exit(0);
		}
	}
	//Main loop flag
	bool bGameActive = true;

	if (SDL_Init( SDL_INIT_GAMECONTROLLER) >= 0) {
		// Do the initializing for getting the game started.
		// Loading images etc.
		nStatus = PrepareForGame();

		if (nStatus != 0) {
			bGameActive = false;
		}

		f_nStartTicks = SDL_GetTicks();
		Uint32 nPreviousLoopStartTime = f_nStartTicks;
		while (bGameActive) {
			Uint32 nNewLoopStartTime = SDL_GetTicks();
			bGameActive = GameLoop(nPreviousLoopStartTime);
			nPreviousLoopStartTime = nNewLoopStartTime;
		}

	} else {
		printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
		nStatus = -1;
	}

	//Quit SDL subsystems
	SDL_Quit();

	TTF_Quit();

	return (nStatus);
}
